﻿#region License
/**	
The MIT License (MIT)

Copyright (c) 2013 Philipp Schillinger, Michael Alekseew, Andreas Braun, Steeven Zeiß

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE. 
 **/
#endregion

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace IndoorLocalization
{
    /// <summary>
    /// Represents an entity such as a human that was found by the sensors.
    /// </summary>
    public class Entity {

		/// <summary>
		/// Provides a unique id for each logical entity.
		/// Can be set when an id is not used anymore.
		/// </summary>
		private static int NextID {
			get {
				int next = 0;
				lock (nextIds) {
					if (nextIds.Count < 4) {
						nextIds.Add(maxID++);
						if (maxID >= 1000) maxID = 0;
					}
					next = nextIds[0];
					nextIds.RemoveAt(0);
				}
				return next;
			}
			set {
				lock (nextIds) {
					nextIds.Add(value);
				}
			}
		}
		/// <summary>
		/// Saves the current maximum id.
		/// Is set automatically by NextID.
		/// </summary>
		private static int maxID = 0;
		/// <summary>
		/// Saves a list of possible next ids, old and invalid ids can be reused.
		/// Is managed automatically by NextID.
		/// </summary>
		private static List<int> nextIds = new List<int>();

		/// <summary>
		/// Saves the unique id of this entity.
		/// If a predecessor exist, its id will be used.
		/// </summary>
		private int id;

		/// <summary>
		/// Position of this entity.
		/// </summary>
		private HCoord position;
		/// <summary>
		/// Velocity vector of this entity.
		/// </summary>
		private HCoord velocity;
		/// <summary>
		/// Accelaration vector of this entity.
		/// </summary>
		private HCoord accelaration;

		/// <summary>
		/// The reliability that this entity really exists at this position.
		/// </summary>
		private float reliability;

		/// <summary>
		/// The unique id of this entity.
		/// If a predecessor exist, its id will be used.
		/// </summary>
		public int ID { get { return id; } }

        /// <summary>
        /// The x position of this entity.
        /// </summary>
        public int X { get { return position.X; } }
		
        /// <summary>
        /// The y position of this entity.
        /// </summary>
        public int Y { get { return position.Y; } }
        /// <summary>
        /// The position of this entity.
        /// </summary>
        public HCoord Position { get { return position; } }

		private byte networkID = 0;
		/// <summary>
		/// An ID to determine where of the network this entity came from.
		/// Equals 0 if entity was created locally.
		/// </summary>
		public byte NetworkID { get { return networkID; } }

        private List<SensorCam> sensors;
        /// <summary>
        /// All sensors that detected this entity.
        /// </summary>
        public List<SensorCam> Sensors { get { return sensors; } }
        private List<SensorRay> rays;
        /// <summary>
        /// All rays that created this entity.
        /// </summary>
        public List<SensorRay> Rays { get { return rays; } }
        /// <summary>
        /// The reliability that this entity really exists at this position.
        /// A value between 0% and 100% dependent on consecutive findings of this entity.
        /// </summary>
        public int Reliability { get { return (int)reliability; } }

        /// <summary>
        /// Creates a new entity at the intersect point of two rays.
        /// Those rays have to intersect, so test it before.
        /// </summary>
        /// <param name="ray1">First ray of the intersect point.</param>
        /// <param name="ray2">Second ray of the intersect point.</param>
        public Entity(SensorRay ray1, SensorRay ray2) {
			if (nextIds.Count == 0)
				NextID = maxID++;	// provide a new id if neccessary

			// the intersect point can be used without evaluation
			// because the user has to assure it is valid
			position = HCoord.Cross(ray1.Line, ray2.Line);
			velocity = new HCoord(0, 0);
			accelaration = new HCoord(0, 0);

			id = NextID;
			reliability = 0;

			sensors = new List<SensorCam>();
			if (ray1.Creator != null) sensors.Add(ray1.CreatorCam);
			if (ray2.Creator != null) sensors.Add(ray2.CreatorCam);

            rays = new List<SensorRay>();
            rays.Add(ray1);
            rays.Add(ray2);
        }

		/// <summary>
		/// Creates a new entity out of entity data.
		/// Considered to be used for creating entities out of data retrieved from the network.
		/// </summary>
		/// <param name="id">ID of the entity to create.</param>
		/// <param name="position">Position of the entity to create.</param>
		/// <param name="reliability">Reliability of the entity to create.</param>
		/// <param name="networkID">NetworkID of the program that originally created this entity.</param>
		public Entity(int id, HCoord position, float reliability, byte networkID) {
			this.position = position;
			velocity = new HCoord(0, 0);
			accelaration = new HCoord(0, 0);

			this.id = id;
			this.reliability = reliability;
			this.networkID = networkID;

			sensors = new List<SensorCam>();
		}

		/// <summary>
		/// Creates a new, guessed entity of an old, reliable one.
		/// </summary>
		/// <param name="predecessor">The old entity.</param>
		public Entity(Entity predecessor) {
            this.id = predecessor.id;
			this.reliability = predecessor.reliability;
			this.sensors = predecessor.sensors;

			this.position = new HCoord(
				predecessor.position.X + predecessor.velocity.X,
				predecessor.position.Y + predecessor.velocity.Y);
			this.velocity = new HCoord(
				predecessor.velocity.X / 2,
				predecessor.velocity.Y / 2);
			this.accelaration = new HCoord(
				0,
				0);
		}

        /// <summary>
        /// Determines if two entities are near and should be combined.
        /// </summary>
        /// <param name="other">Entity that could be near.</param>
        /// <returns><c>true</c> if near, <c>false</c> otherwise.</returns>
        public bool IsNear(Entity other) {
			return position.DistanceTo(other.position) < Program.EntityNearDistance;
        }

		/// <summary>
		/// Determines if this entity could be the next version of the given one.
		/// This means both of them represent the same object or person, but in
		/// consecutive frames.
		/// </summary>
		/// <param name="other">The probably older entity.</param>
		/// <returns><c>true</c> if this is the next one, otherwise <c>false</c>.</returns>
		public bool IsNextOf(Entity other) {
			// Calculate estimated new position of the entity
			HCoord estimatedPosition = new HCoord(
							other.position.X + other.velocity.X + other.accelaration.X,
							other.position.Y + other.velocity.Y + other.accelaration.Y);

			// Compare it with the new position
			return position.DistanceTo(estimatedPosition) < Program.EntitySequelTolerance;
		}

        /// <summary>
        /// Modifies this entity in such a way that it represents an average of itself
        /// and the given other entity.
		/// It is assumed that those entities are "new" which means they don't have any
		/// velocity or accelaration (but they could be a sequel and therefor get a
		/// velocity and accelaration).
        /// </summary>
        /// <param name="other">The entity to combine with.</param>
        public void Combine(Entity other) {
			// release unused id
			NextID = other.id;

            // combine position
            position.X = (this.X + other.X) / 2;
            position.Y = (this.Y + other.Y) / 2;

            // add additional sensors and rays
            foreach (SensorCam sensor in other.Sensors) {
                if (!sensors.Contains(sensor))
                    sensors.Add(sensor);
            }
            foreach (SensorRay ray in other.Rays) {
                if (!rays.Contains(ray))
                    rays.Add(ray);
            }
        }

		/// <summary>
		/// Updates the velocity and accelaration values of this entity assuming
		/// the given one as its predecessor. Also updates references.
		/// </summary>
		/// <param name="predecessor"></param>
		public void Sequel(Entity predecessor) {
			// take old identity
			NextID = this.id;
			this.id = predecessor.id;

			// take reliability
			this.reliability = predecessor.reliability;

			// calculate Velocity
            this.velocity.X = (4*predecessor.velocity.X + this.position.X - predecessor.position.X)/5;
			this.velocity.Y = (4*predecessor.velocity.Y + this.position.Y - predecessor.position.Y)/5;

			// calculate Accelaration
			this.accelaration.X = (4*predecessor.accelaration.X + this.velocity.X - predecessor.velocity.X)/5;
			this.accelaration.Y = (4*predecessor.accelaration.Y + this.velocity.Y - predecessor.velocity.Y)/5;
		}

		/// <summary>
		/// Increases the reliability of this entity.
		/// Should be used if this entity is found again.
		/// </summary>
		public void IncreaseReliability() {
			reliability = Math.Min(100, reliability + (120-reliability)/50);
		}

		/// <summary>
		/// Decreases the reliability of this entity.
		/// Should be used each time this entity is just guessed.
		/// </summary>
		public void DecreaseReliability() {
			reliability = Math.Max(0, reliability - 1.5f);
		}
    }
}
